/*++
Copyright (c) 1997-2000  Microsoft Corporation All Rights Reserved

Module Name:
    mintopo.cpp

Abstract:
    Implementation of topology miniport.
--*/

#pragma warning (disable : 4127)

#include "scream.h"
#include "common.h"
#include "minwave.h"
#include "mintopo.h"
#include "toptable.h"


PHYSICALCONNECTIONTABLE TopologyPhysicalConnections =
{
    KSPIN_TOPO_WAVEOUT_SOURCE,  // TopologyIn
    KSPIN_TOPO_WAVEIN_BRIDGE,   // TopologyOut
    KSPIN_WAVE_WAVEIN_BRIDGE,   // WaveIn
    KSPIN_WAVE_RENDER_SOURCE    // WaveOut
};

VOID NotifyMicConnectionChange(PCMiniportTopology pmt)
{
    if (pmt->m_PortEvents) {
        pmt->m_PortEvents->GenerateEventList(
            (GUID*)&KSEVENTSETID_PinCapsChange, // event set. NULL is a wild card for all events.
            KSEVENT_PINCAPS_JACKINFOCHANGE,     // event ID.
            TRUE,                               // use pid ID.
            KSPIN_TOPO_MIC_ELEMENTS,            // pin ID.
            FALSE,                              // do not use node ID.
            ULONG(-1));                         // node ID, not used.
    }
}

VOID NotifyVolumeChange(PCMiniportTopology pmt)
{
    if (pmt->m_PortEvents) {
        pmt->m_PortEvents->GenerateEventList(
            (GUID*)&KSEVENTSETID_AudioControlChange, // event set. NULL is a wild card for all events.
            KSEVENT_CONTROL_CHANGE,             // event ID.
            FALSE,                              // do not use pid ID.
            ULONG(-1),                          // pin ID, not used.
            TRUE,                               // use node ID
            KSNODE_TOPO_LINEOUT_VOLUME);        // node ID.
    }
}

VOID StatusChangeNotifyhandler(PCMiniportTopology pmt)
{
    UINT32 newStatus = pmt->GetStatusFromBackend();
    if (newStatus == INVALID_IVSHMEM_STATUS) {
        DPF(D_ERROR, ("StatusChangeNotifyhandler: Failed for INVALID_IVSHMEM_STATUS\n"));
        return;
    }
    UINT32 oldStatus = pmt->GetSavedStatus();
    pmt->SaveStatus(newStatus);
    UINT32 diff = newStatus ^ oldStatus;
    if (diff & IVSHMEM_STATUS_MIC_AVAIL_BIT) {
        NotifyMicConnectionChange(pmt);
    }
}

VOID SetVolumeToBackend(PCMiniportTopology pmt)
{
    PADAPTERCOMMON pAdapterCommon = pmt->GetAdapterCommonObject();
    if (!pAdapterCommon) {
        DPF(D_ERROR, ("AdapterCommon object is invalid"));
        return;
    }

    pAdapterCommon->SetVolumeToBackend(pmt->GetGuestVolume());
}

VOID IvshmemEventHandler(PCMiniportTopology pmt, UINT64 EventsNum)
{
    switch (EventsNum) {
        case VolumeSyncFromBackend:
            NotifyVolumeChange(pmt);
            break;
        case StatusChangeNotify:
            StatusChangeNotifyhandler(pmt);
            break;
        case VolumeSyncToBackend:
            SetVolumeToBackend(pmt);
            break;
        default:
            break;
    }
}

VOID IvshmemEventDispatchWorkItem(PDEVICE_OBJECT DeviceObject, PVOID Ctx)
{
    UNREFERENCED_PARAMETER(DeviceObject);
    PWORKITEM_PARAM item = (PWORKITEM_PARAM)Ctx;
    for (UINT64 i = 0; item->EventsMask; i++) {
        if (item->EventsMask & 1) {
            IvshmemEventHandler((PCMiniportTopology)item->ctx, i);
        }
        item->EventsMask >>= 1;
    }
    InterlockedExchange(&item->Used, 0);
}

VOID QueueIvshmemEventWorkItem(PVOID Context, UINT64 EventsMask)
{
    PCMiniportTopology pmt = (PCMiniportTopology)Context;
    PWORKITEM_PARAM item = pmt->GetWorkItem();
    if (item == NULL) {
        return;
    }

    item->ctx = (PVOID)pmt;
    item->EventsMask = EventsMask;
    IoQueueWorkItem(item->WorkItem, IvshmemEventDispatchWorkItem,
        CriticalWorkQueue, (PVOID)item);
}

static VOID QueueVolumeSyncToBackendEventWorkItem(PEX_TIMER Timer, PVOID Context)
{
    UNREFERENCED_PARAMETER(Timer);

    PCMiniportTopology pmt = (PCMiniportTopology)Context;
    PWORKITEM_PARAM item = pmt->GetWorkItem();
    if (item == NULL) {
        return;
    }

    item->ctx = (PVOID)pmt;
    item->EventsMask = 0x01 << VolumeSyncToBackend;
    IoQueueWorkItem(item->WorkItem, IvshmemEventDispatchWorkItem,
        CriticalWorkQueue, (PVOID)item);
}

//=============================================================================
NTSTATUS CreateMiniportTopologyMSVAD (
    OUT PUNKNOWN* Unknown,
    IN  REFCLSID,
    IN  PUNKNOWN  UnknownOuter OPTIONAL,
    IN  POOL_TYPE PoolType 
)
/*++
Routine Description:
    Creates a new topology miniport.

Arguments:
  Unknown - 
  RefclsId -
  UnknownOuter -
  PoolType - 

Return Value:
  NT status code.
--*/
{
    ASSERT(Unknown);

    STD_CREATE_BODY_(CMiniportTopology, Unknown, UnknownOuter, PoolType, PMINIPORTTOPOLOGY);
} // CreateMiniportTopologyMSVAD

//=============================================================================
CMiniportTopology::CMiniportTopology(PUNKNOWN outer) : CUnknown(outer)
/*++
Routine Description:
  Topology miniport constructor

Arguments:

Return Value:
  void
--*/
{
    DPF_ENTER(("[%s]",__FUNCTION__));

    m_AdapterCommon = NULL;
    m_FilterDescriptor = NULL;
    m_PortEvents = NULL;
    m_isIvshmemEventRegister = FALSE;
    m_BackendStatus = INVALID_IVSHMEM_STATUS;
    m_pWorkItems = NULL;
    m_Timer = NULL;
    m_Volume = 0;
    m_isFirstVolumeSyncToBackend = TRUE;
} // CMiniportTopology

CMiniportTopology::~CMiniportTopology(void)
/*++
Routine Description:
  Topology miniport destructor

Arguments:

Return Value:
  NT status code.
--*/
{
    DPF_ENTER(("[%s]",__FUNCTION__));

    if (!m_Timer) {
        ExDeleteTimer(m_Timer, TRUE, FALSE, NULL);
        m_Timer = NULL;
    }

    if (m_AdapterCommon) {
        m_AdapterCommon->RegisterIvshmemEventHandlerDispatch((PVOID)this, NULL);
        m_AdapterCommon->Release();
    }
    DestroyWorkItems();
} // ~CMiniportTopology

//=============================================================================
NTSTATUS CMiniportTopology::DataRangeIntersection( 
    IN  ULONG        PinId,
    IN  PKSDATARANGE ClientDataRange,
    IN  PKSDATARANGE MyDataRange,
    IN  ULONG        OutputBufferLength,
    OUT PVOID        ResultantFormat OPTIONAL,
    OUT PULONG       ResultantFormatLength 
)
/*++
Routine Description:
  The DataRangeIntersection function determines the highest quality 
  intersection of two data ranges.

Arguments:
  PinId - Pin for which data intersection is being determined. 
  ClientDataRange - Pointer to KSDATARANGE structure which contains the data range 
                    submitted by client in the data range intersection property 
                    request. 
  MyDataRange - Pin's data range to be compared with client's data range. 
  OutputBufferLength - Size of the buffer pointed to by the resultant format 
                       parameter. 
  ResultantFormat - Pointer to value where the resultant format should be 
                    returned. 
  ResultantFormatLength - Actual length of the resultant format that is placed 
                          at ResultantFormat. This should be less than or equal 
                          to OutputBufferLength. 

Return Value:
  NT status code.
--*/
{
    UNREFERENCED_PARAMETER(PinId);
    UNREFERENCED_PARAMETER(ClientDataRange);
    UNREFERENCED_PARAMETER(MyDataRange);
    UNREFERENCED_PARAMETER(OutputBufferLength);
    UNREFERENCED_PARAMETER(ResultantFormat);
    UNREFERENCED_PARAMETER(ResultantFormatLength);

    DPF_ENTER(("[%s]",__FUNCTION__));

    return (STATUS_NOT_IMPLEMENTED);
} // DataRangeIntersection

//=============================================================================
STDMETHODIMP CMiniportTopology::GetDescription(OUT PPCFILTER_DESCRIPTOR * OutFilterDescriptor)
/*++
Routine Description:
  The GetDescription function gets a pointer to a filter description. 
  It provides a location to deposit a pointer in miniport's description 
  structure. This is the placeholder for the FromNode or ToNode fields in 
  connections which describe connections to the filter's pins. 

Arguments:
  OutFilterDescriptor - Pointer to the filter description. 

Return Value:
  NT status code.
--*/
{
    ASSERT(OutFilterDescriptor);

    DPF_ENTER(("[%s]",__FUNCTION__));

    *OutFilterDescriptor = m_FilterDescriptor;

    return (STATUS_SUCCESS);
} // GetDescription

VOID CMiniportTopology::TopologyMixerVolumeWrite(UINT32 volume)
{
    if (volume > MAX_VOLUME_VAL) {
        DPF(D_ERROR, ("TopologyMixerVolumeWrite: Illegal volume %d\n", volume));
        return;
    }
    m_AdapterCommon->MixerVolumeWrite(KSNODE_TOPO_LINEOUT_VOLUME, 0, volume);
}

//=============================================================================
STDMETHODIMP CMiniportTopology::Init( 
    IN PUNKNOWN      UnknownAdapter,
    IN PRESOURCELIST ResourceList,
    IN PPORTTOPOLOGY Port_ 
)
/*++
Routine Description:
  The Init function initializes the miniport. Callers of this function 
  should run at IRQL PASSIVE_LEVEL

Arguments:
  UnknownAdapter - A pointer to the Iuknown interface of the adapter object. 
  ResourceList - Pointer to the resource list to be supplied to the miniport 
                 during initialization. The port driver is free to examine the 
                 contents of the ResourceList. The port driver will not be 
                 modify the ResourceList contents. 
  Port - Pointer to the topology port object that is linked with this miniport. 

Return Value:
  NT status code.
--*/
{
    UNREFERENCED_PARAMETER(ResourceList);
    UNREFERENCED_PARAMETER(Port_);

    ASSERT(UnknownAdapter);
    ASSERT(Port_);

    DPF_ENTER(("[CMiniportTopology::Init]"));

    NTSTATUS ntStatus;
    CLIENT_ID       ClientID = { 0 };

    ntStatus = UnknownAdapter->QueryInterface(IID_IAdapterCommon, (PVOID *) &m_AdapterCommon);
    if (NT_SUCCESS(ntStatus)) {
        //
        // Get the port event interface.
        //
        ntStatus = Port_->QueryInterface(IID_IPortEvents, (PVOID*)&m_PortEvents);
    }

    if (NT_SUCCESS(ntStatus)) {
        ntStatus = InitializeWorkItems(m_AdapterCommon->GetDeviceObject());
    }

    if (NT_SUCCESS(ntStatus)) {
        m_AdapterCommon->MixerReset();
        m_FilterDescriptor = &ExtMiniportFilterDescriptor;
    } else {
        DestroyWorkItems();
        // clean up AdapterCommon
        if (m_AdapterCommon) {
            m_AdapterCommon->Release();
            m_AdapterCommon = NULL;
        }
    }

    m_Timer = ExAllocateTimer(QueueVolumeSyncToBackendEventWorkItem, this, EX_TIMER_HIGH_RESOLUTION);
    if (!m_Timer) {
        DPF_ENTER(("Failed to allocate timer\n"));
        ntStatus = STATUS_UNSUCCESSFUL;
    }

    return ntStatus;
} // Init

//=============================================================================
PADAPTERCOMMON CMiniportTopology::GetAdapterCommonObject()
/*++
Routine Description:
  Returns the AdapterCommonObject

Arguments:

Return Value:
  PADAPTERCOMMON
--*/
{
    PAGED_CODE();

    return m_AdapterCommon;
}


//=============================================================================
UINT32 CMiniportTopology::GetGuestVolume()
/*++
Routine Description:
    Get Guest Volume.
--*/
{
    return m_Volume;
}

//=============================================================================
VOID CMiniportTopology::RecordGuestVolume(UINT32 volume)
/*++
Routine Description:
    Record Guest Volume.
--*/
{
    m_Volume = volume;
}

STDMETHODIMP_(void) CMiniportTopology::PowerChangeNotify(IN POWER_STATE PowerState)
{
    switch (PowerState.DeviceState) {
        case PowerDeviceD0:
            if (m_AdapterCommon) {
                StatusChangeNotifyhandler(this);
                NotifyVolumeChange(this);
            }
            break;
        case PowerDeviceD3:
            if (m_Timer) {
                ExCancelTimer(m_Timer, NULL);
            }
            m_isFirstVolumeSyncToBackend = TRUE;
            break;
        default:
            break;
    }
}

//=============================================================================
STDMETHODIMP CMiniportTopology::NonDelegatingQueryInterface( 
    IN  REFIID Interface,
    OUT PVOID* Object 
)
/*++
Routine Description:
  QueryInterface for MiniportTopology

Arguments:
  Interface - GUID of the interface
  Object - interface object to be returned.

Return Value:
  NT status code.
--*/
{
    ASSERT(Object);

    if (IsEqualGUIDAligned(Interface, IID_IUnknown)) {
        *Object = PVOID(PUNKNOWN(PMINIPORTTOPOLOGY(this)));
    } else if (IsEqualGUIDAligned(Interface, IID_IMiniport)) {
        *Object = PVOID(PMINIPORT(this));
    } else if (IsEqualGUIDAligned(Interface, IID_IMiniportTopology)) {
        *Object = PVOID(PMINIPORTTOPOLOGY(this));
    } else if (IsEqualGUIDAligned(Interface, IID_IPowerNotify)) {
        *Object = PVOID(PPOWERNOTIFY(this));
    } else {
        *Object = NULL;
    }

    if (*Object) {
        // We reference the interface for the caller.
        PUNKNOWN(*Object)->AddRef();
        return(STATUS_SUCCESS);
    }

    return(STATUS_INVALID_PARAMETER);
} // NonDelegatingQueryInterface

//=============================================================================
UINT32 CMiniportTopology::GetSavedStatus()
/*++
Routine Description:
  return recorded backend status register.
--*/
{
    if (m_BackendStatus == INVALID_IVSHMEM_STATUS) {
        m_BackendStatus = GetStatusFromBackend();
    }
    return m_BackendStatus;
}

//=============================================================================
VOID CMiniportTopology::SaveStatus(UINT32 status)
/*++
Routine Description:
  save latest backend status register.
--*/
{
    m_BackendStatus = status;
}

//=============================================================================
NTSTATUS CMiniportTopology::PropertyHandlerBasicSupportVolume(IN PPCPROPERTY_REQUEST PropertyRequest)
/*++
Routine Description:
  Handles BasicSupport for Volume nodes.

Arguments:
  PropertyRequest - property request structure

Return Value:
  NT status code.
--*/
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    ULONG    cbFullProperty = sizeof(KSPROPERTY_DESCRIPTION) + sizeof(KSPROPERTY_MEMBERSHEADER) + sizeof(KSPROPERTY_STEPPING_LONG);

    if (PropertyRequest->ValueSize >= (sizeof(KSPROPERTY_DESCRIPTION))) {
        PKSPROPERTY_DESCRIPTION PropDesc = PKSPROPERTY_DESCRIPTION(PropertyRequest->Value);
        PropDesc->AccessFlags       = KSPROPERTY_TYPE_ALL;
        PropDesc->DescriptionSize   = cbFullProperty;
        PropDesc->PropTypeSet.Set   = KSPROPTYPESETID_General;
        PropDesc->PropTypeSet.Id    = VT_I4;
        PropDesc->PropTypeSet.Flags = 0;
        PropDesc->MembersListCount  = 1;
        PropDesc->Reserved          = 0;

        // if return buffer can also hold a range description, return it too
        if(PropertyRequest->ValueSize >= cbFullProperty) {
            // fill in the members header
            PKSPROPERTY_MEMBERSHEADER Members = PKSPROPERTY_MEMBERSHEADER(PropDesc + 1);
            Members->MembersFlags   = KSPROPERTY_MEMBER_STEPPEDRANGES;
            Members->MembersSize    = sizeof(KSPROPERTY_STEPPING_LONG);
            Members->MembersCount   = 1;
            Members->Flags          = KSPROPERTY_MEMBER_FLAG_BASICSUPPORT_MULTICHANNEL;

            // fill in the stepped range
            PKSPROPERTY_STEPPING_LONG Range = PKSPROPERTY_STEPPING_LONG(Members + 1);
            Range->Bounds.SignedMaximum = MAX_VOLUME_VAL;
            Range->Bounds.SignedMinimum = MIN_VOLUME_VAL;
            Range->SteppingDelta        = VOLUME_STEP;
            Range->Reserved             = 0;

            // set the return value size
            PropertyRequest->ValueSize = cbFullProperty;
        } else {
            PropertyRequest->ValueSize = sizeof(KSPROPERTY_DESCRIPTION);
        }
    } else if(PropertyRequest->ValueSize >= sizeof(ULONG)) {
        // if return buffer can hold a ULONG, return the access flags
        PULONG AccessFlags = PULONG(PropertyRequest->Value);

        PropertyRequest->ValueSize = sizeof(ULONG);
        *AccessFlags = KSPROPERTY_TYPE_ALL;
    } else {
        PropertyRequest->ValueSize = 0;
        ntStatus = STATUS_BUFFER_TOO_SMALL;
    }

    return ntStatus;
} // PropertyHandlerBasicSupportVolume

//=============================================================================
NTSTATUS CMiniportTopology::PropertyHandlerCpuResources(IN PPCPROPERTY_REQUEST PropertyRequest)
/*++
Routine Description:
  Processes KSPROPERTY_AUDIO_CPURESOURCES

Arguments:
  PropertyRequest - property request structure

Return Value:
  NT status code.
--*/
{
    DPF_ENTER(("[%s]",__FUNCTION__));

    NTSTATUS ntStatus = STATUS_INVALID_DEVICE_REQUEST;

    if (PropertyRequest->Verb & KSPROPERTY_TYPE_BASICSUPPORT) {
        ntStatus = PropertyHandler_BasicSupport(PropertyRequest, KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_BASICSUPPORT, VT_ILLEGAL);
    } else if (PropertyRequest->Verb & KSPROPERTY_TYPE_GET) {
        ntStatus = ValidatePropertyParams(PropertyRequest, sizeof(ULONG));
        if (NT_SUCCESS(ntStatus)) {
            *(PLONG(PropertyRequest->Value)) = KSAUDIO_CPU_RESOURCES_HOST_CPU;
            PropertyRequest->ValueSize = sizeof(LONG);
        }
    }

    return ntStatus;
} // PropertyHandlerCpuResources

//=============================================================================
NTSTATUS CMiniportTopology::PropertyHandlerGeneric(IN PPCPROPERTY_REQUEST PropertyRequest)
/*++
Routine Description:
  Handles all properties for this miniport.

Arguments:
  PropertyRequest - property request structure

Return Value:
  NT status code.
--*/
{
    NTSTATUS ntStatus = STATUS_INVALID_DEVICE_REQUEST;

    switch (PropertyRequest->PropertyItem->Id) {
        case KSPROPERTY_AUDIO_VOLUMELEVEL:
            ntStatus = PropertyHandlerVolume(PropertyRequest);
            break;
        
        case KSPROPERTY_AUDIO_CPU_RESOURCES:
            ntStatus = PropertyHandlerCpuResources(PropertyRequest);
            break;

        case KSPROPERTY_AUDIO_MUTE:
            ntStatus = PropertyHandlerMute(PropertyRequest);
            break;

        case KSPROPERTY_AUDIO_MUX_SOURCE:
            ntStatus = PropertyHandlerMuxSource(PropertyRequest);
            break;

        case KSPROPERTY_AUDIO_DEV_SPECIFIC:
            ntStatus = PropertyHandlerDevSpecific(PropertyRequest);
            break;

        default:
            DPF(D_TERSE, ("[PropertyHandlerGeneric: Invalid Device Request]"));
    }

    return ntStatus;
} // PropertyHandlerGeneric

//=============================================================================
NTSTATUS CMiniportTopology::PropertyHandlerMute(IN PPCPROPERTY_REQUEST PropertyRequest)
/*++
Routine Description:
  Property handler for KSPROPERTY_AUDIO_MUTE

Arguments:
  PropertyRequest - property request structure

Return Value:
  NT status code.
--*/
{
    DPF_ENTER(("[%s]",__FUNCTION__));

    NTSTATUS ntStatus;
    LONG     lChannel;
    PBOOL    pfMute;

    if (PropertyRequest->Verb & KSPROPERTY_TYPE_BASICSUPPORT) {
        ntStatus = PropertyHandler_BasicSupport(PropertyRequest, KSPROPERTY_TYPE_ALL, VT_BOOL);
    } else {
        ntStatus = ValidatePropertyParams(PropertyRequest, sizeof(BOOL), sizeof(LONG));
        if (NT_SUCCESS(ntStatus)) {
            lChannel = * PLONG (PropertyRequest->Instance);
            pfMute   = PBOOL (PropertyRequest->Value);

            // Currently we only support mono and stero channels.
            // For other cases, report invalid parameter.
            // Otherwise Windows would unmute automatically.
            if (lChannel != 1 && lChannel != 2) {
                DPF(D_TERSE, ("[%s - invalid lChannel=%d]", __FUNCTION__, lChannel));
                return STATUS_INVALID_PARAMETER;
            }

            if (PropertyRequest->Verb & KSPROPERTY_TYPE_GET) {
                *pfMute = m_AdapterCommon->MixerMuteRead(PropertyRequest->Node);
                PropertyRequest->ValueSize = sizeof(BOOL);
            } else if (PropertyRequest->Verb & KSPROPERTY_TYPE_SET) {
                m_AdapterCommon->MixerMuteWrite(PropertyRequest->Node, *pfMute);
            }
        } else {
            DPF(D_TERSE, ("[%s - ntStatus=0x%08x]", __FUNCTION__, ntStatus));
        }
    }

    return ntStatus;
} // PropertyHandlerMute

//=============================================================================
NTSTATUS CMiniportTopology::PropertyHandlerMuxSource(IN PPCPROPERTY_REQUEST PropertyRequest)
/*++
Routine Description:
  PropertyHandler for KSPROPERTY_AUDIO_MUX_SOURCE.

Arguments:
  PropertyRequest - property request structure

Return Value:
  NT status code.
--*/
{
    DPF_ENTER(("[%s]",__FUNCTION__));

    NTSTATUS ntStatus = STATUS_INVALID_DEVICE_REQUEST;

    //
    // Validate node
    // This property is only valid for WAVEIN_MUX node.
    //
    // TODO if (WAVEIN_MUX == PropertyRequest->Node)
    {
        if (PropertyRequest->ValueSize >= sizeof(ULONG)) {
            PULONG pulMuxValue = PULONG(PropertyRequest->Value);
            
            if (PropertyRequest->Verb & KSPROPERTY_TYPE_GET) {
                *pulMuxValue = m_AdapterCommon->MixerMuxRead();
                PropertyRequest->ValueSize = sizeof(ULONG);
                ntStatus = STATUS_SUCCESS;
            } else if (PropertyRequest->Verb & KSPROPERTY_TYPE_SET) {
                m_AdapterCommon->MixerMuxWrite(*pulMuxValue);
                ntStatus = STATUS_SUCCESS;
            } else if (PropertyRequest->Verb & KSPROPERTY_TYPE_BASICSUPPORT) {
                ntStatus = PropertyHandler_BasicSupport(PropertyRequest, KSPROPERTY_TYPE_ALL, VT_I4);
            }
        } else {
            DPF(D_TERSE, ("[PropertyHandlerMuxSource - Invalid parameter]"));
            ntStatus = STATUS_INVALID_PARAMETER;
        }
    }

    return ntStatus;
} // PropertyHandlerMuxSource

//=============================================================================
NTSTATUS CMiniportTopology::PropertyHandlerVolume(IN PPCPROPERTY_REQUEST PropertyRequest)
/*++
Routine Description:
  Property handler for KSPROPERTY_AUDIO_VOLUMELEVEL

Arguments:
  PropertyRequest - property request structure

Return Value:
  NT status code.
--*/
{
    DPF_ENTER(("[%s]",__FUNCTION__));

    NTSTATUS ntStatus = STATUS_INVALID_DEVICE_REQUEST;
    LONG     lChannel;
    PULONG   pulVolume;

    if (PropertyRequest->Verb & KSPROPERTY_TYPE_BASICSUPPORT) {
        ntStatus = PropertyHandlerBasicSupportVolume(PropertyRequest);
    } else {
        if (!m_isIvshmemEventRegister) {
            m_isIvshmemEventRegister =
                m_AdapterCommon->RegisterIvshmemEventHandlerDispatch((PVOID)this, QueueIvshmemEventWorkItem);
        }
        // volume value is a ULONG, instance is the channel number
        ntStatus = ValidatePropertyParams(PropertyRequest, sizeof(ULONG), sizeof(LONG));
        if (NT_SUCCESS(ntStatus)) {
            lChannel  = * (PLONG (PropertyRequest->Instance));
            pulVolume = PULONG (PropertyRequest->Value);

            if (PropertyRequest->Verb & KSPROPERTY_TYPE_GET) {
                if (PropertyRequest->Node == KSNODE_TOPO_LINEOUT_VOLUME && lChannel == 0) {
                    TopologyMixerVolumeWrite(m_AdapterCommon->GetVolumeFromBackend());
                }
                *pulVolume = m_AdapterCommon->MixerVolumeRead(PropertyRequest->Node, lChannel);
                PropertyRequest->ValueSize = sizeof(ULONG);
            } else if (PropertyRequest->Verb & KSPROPERTY_TYPE_SET) {
                m_AdapterCommon->MixerVolumeWrite(PropertyRequest->Node, lChannel, *pulVolume);
                if (PropertyRequest->Node == KSNODE_TOPO_LINEOUT_VOLUME && lChannel == 0) {
                    // During Windows startup there is an active sync of the volume to the backend,
                    // we ignore this volume sync
                    if (m_isFirstVolumeSyncToBackend) {
                        m_isFirstVolumeSyncToBackend = FALSE;
                    } else {
                        RecordGuestVolume(*pulVolume);
                        // Trigger time in 100-nanosecond intervals. A negative value represents a relative time.
                        // Here, it triggers after a 50-millisecond delay (-500000 * 100ns = 50ms)
                        ExSetTimer(m_Timer, -500000, 0, NULL);
                    }
                }
            }
        } else {
            DPF(D_TERSE, ("[%s - ntStatus=0x%08x]",__FUNCTION__,ntStatus));
        }
    }

    return ntStatus;
} // PropertyHandlerVolume

//=============================================================================
NTSTATUS CMiniportTopology::PropertyHandlerDevSpecific(IN PPCPROPERTY_REQUEST PropertyRequest)
/*++
Routine Description:
  Property handler for KSPROPERTY_AUDIO_DEV_SPECIFIC

Arguments:
  PropertyRequest - property request structure

Return Value:
  NT status code.
--*/
{
    DPF_ENTER(("[%s]",__FUNCTION__));

    NTSTATUS ntStatus = STATUS_SUCCESS;

    if (PropertyRequest->Verb & KSPROPERTY_TYPE_BASICSUPPORT) {
        if (DEV_SPECIFIC_VT_BOOL == PropertyRequest->Node ) {
            ntStatus = PropertyHandler_BasicSupport(PropertyRequest,KSPROPERTY_TYPE_ALL,VT_BOOL);
        } else {
            ULONG ExpectedSize = sizeof( KSPROPERTY_DESCRIPTION ) + sizeof( KSPROPERTY_MEMBERSHEADER ) + sizeof( KSPROPERTY_BOUNDS_LONG );
            DWORD ulPropTypeSetId;

            if( DEV_SPECIFIC_VT_I4 == PropertyRequest->Node ) {
                ulPropTypeSetId = VT_I4;
            } else if ( DEV_SPECIFIC_VT_UI4 == PropertyRequest->Node ) {
                ulPropTypeSetId = VT_UI4;
            } else {
                ulPropTypeSetId = VT_ILLEGAL;
                ntStatus = STATUS_INVALID_PARAMETER;
            }

            if( NT_SUCCESS(ntStatus)) {
                if ( !PropertyRequest->ValueSize ) {
                    PropertyRequest->ValueSize = ExpectedSize;
                    ntStatus = STATUS_BUFFER_OVERFLOW;
                } else if (PropertyRequest->ValueSize >= sizeof(KSPROPERTY_DESCRIPTION)) {
                    // if return buffer can hold a KSPROPERTY_DESCRIPTION, return it
                    //
                    PKSPROPERTY_DESCRIPTION PropDesc = PKSPROPERTY_DESCRIPTION(PropertyRequest->Value);
                    PropDesc->AccessFlags       = KSPROPERTY_TYPE_ALL;
                    PropDesc->DescriptionSize   = ExpectedSize;
                    PropDesc->PropTypeSet.Set   = KSPROPTYPESETID_General;
                    PropDesc->PropTypeSet.Id    = ulPropTypeSetId;
                    PropDesc->PropTypeSet.Flags = 0;
                    PropDesc->MembersListCount  = 0;
                    PropDesc->Reserved          = 0;

                    if ( PropertyRequest->ValueSize >= ExpectedSize ) {
                        // Extra information to return
                        PropDesc->MembersListCount  = 1;

                        PKSPROPERTY_MEMBERSHEADER MembersHeader = ( PKSPROPERTY_MEMBERSHEADER )( PropDesc + 1);
                        MembersHeader->MembersFlags = KSPROPERTY_MEMBER_RANGES;
                        MembersHeader->MembersCount  = 1;
                        MembersHeader->MembersSize   = sizeof( KSPROPERTY_BOUNDS_LONG );
                        MembersHeader->Flags = 0;

                        PKSPROPERTY_BOUNDS_LONG PeakMeterBounds = (PKSPROPERTY_BOUNDS_LONG)( MembersHeader + 1);
                        if(VT_I4 == ulPropTypeSetId ) {
                            PeakMeterBounds->SignedMinimum = 0;
                            PeakMeterBounds->SignedMaximum = 0x7fffffff;
                        } else {
                            PeakMeterBounds->UnsignedMinimum = 0;
                            PeakMeterBounds->UnsignedMaximum = 0xffffffff;
                        }

                        // set the return value size
                        PropertyRequest->ValueSize = ExpectedSize;
                    } else {
                        // No extra information to return.
                        PropertyRequest->ValueSize = sizeof(KSPROPERTY_DESCRIPTION);
                    }

                    ntStatus = STATUS_SUCCESS;
                } else if (PropertyRequest->ValueSize >= sizeof(ULONG)) {
                    // if return buffer can hold a ULONG, return the access flags
                    //
                    *(PULONG(PropertyRequest->Value)) = KSPROPERTY_TYPE_ALL;

                    PropertyRequest->ValueSize = sizeof(ULONG);
                    ntStatus = STATUS_SUCCESS;                    
                } else {
                    PropertyRequest->ValueSize = 0;
                    ntStatus = STATUS_BUFFER_TOO_SMALL;
                }
            }
        }
    } else {
        // switch on node id
        switch( PropertyRequest->Node ) {
            case DEV_SPECIFIC_VT_BOOL:
                PBOOL pbDevSpecific;

                ntStatus = ValidatePropertyParams(PropertyRequest, sizeof(BOOL), 0);

                if (NT_SUCCESS(ntStatus)) {
                    pbDevSpecific = PBOOL (PropertyRequest->Value);

                    if (PropertyRequest->Verb & KSPROPERTY_TYPE_GET) {
                        *pbDevSpecific = m_AdapterCommon->bDevSpecificRead();
                        PropertyRequest->ValueSize = sizeof(BOOL);
                    } else if (PropertyRequest->Verb & KSPROPERTY_TYPE_SET) {
                        m_AdapterCommon->bDevSpecificWrite(*pbDevSpecific);
                    } else {
                        ntStatus = STATUS_INVALID_PARAMETER;
                    }
                }
                break;
            case DEV_SPECIFIC_VT_I4:
                INT* piDevSpecific;

                ntStatus = ValidatePropertyParams(PropertyRequest, sizeof(int), 0);

                if (NT_SUCCESS(ntStatus)) {
                    piDevSpecific = PINT (PropertyRequest->Value);

                    if (PropertyRequest->Verb & KSPROPERTY_TYPE_GET) {
                        *piDevSpecific = m_AdapterCommon->iDevSpecificRead();
                        PropertyRequest->ValueSize = sizeof(int);
                    } else if (PropertyRequest->Verb & KSPROPERTY_TYPE_SET) {
                        m_AdapterCommon->iDevSpecificWrite(*piDevSpecific);
                    } else {
                        ntStatus = STATUS_INVALID_PARAMETER;
                    }
                }
                break;
            case DEV_SPECIFIC_VT_UI4:
                UINT* puiDevSpecific;

                ntStatus = ValidatePropertyParams(PropertyRequest, sizeof(UINT), 0);

                if (NT_SUCCESS(ntStatus)) {
                    puiDevSpecific = PUINT (PropertyRequest->Value);

                    if (PropertyRequest->Verb & KSPROPERTY_TYPE_GET) {
                        *puiDevSpecific = m_AdapterCommon->uiDevSpecificRead();
                        PropertyRequest->ValueSize = sizeof(UINT);
                    } else if (PropertyRequest->Verb & KSPROPERTY_TYPE_SET) {
                        m_AdapterCommon->uiDevSpecificWrite(*puiDevSpecific);
                    } else {
                        ntStatus = STATUS_INVALID_PARAMETER;
                    }
                }
                break;
            default:
                ntStatus = STATUS_INVALID_PARAMETER;
                break;
        }

        if( !NT_SUCCESS(ntStatus)) {
            DPF(D_TERSE, ("[%s - ntStatus=0x%08x]",__FUNCTION__,ntStatus));
        }
    }

    return ntStatus;
} // PropertyHandlerDevSpecific

//=============================================================================
NTSTATUS CMiniportTopology::PropertyHandlerJackDescription(IN PPCPROPERTY_REQUEST PropertyRequest)
/*++
Routine Description:
  Handles ( KSPROPSETID_Jack, KSPROPERTY_JACK_DESCRIPTION )

Arguments:
  PropertyRequest - 

Return Value:
  NT status code.
--*/
{
    ASSERT(PropertyRequest);

    DPF_ENTER(("[PropertyHandlerJackDescription]"));

    NTSTATUS ntStatus = STATUS_INVALID_DEVICE_REQUEST;
    ULONG    nPinId   = (ULONG)-1;

    if (PropertyRequest->InstanceSize >= sizeof(ULONG)) {
        nPinId = *(PULONG(PropertyRequest->Instance));

        if ((nPinId < ARRAYSIZE(JackDescriptions)) && (JackDescriptions[nPinId] != NULL)) {
            if (PropertyRequest->Verb & KSPROPERTY_TYPE_BASICSUPPORT) {
                ntStatus = PropertyHandler_BasicSupport(PropertyRequest, KSPROPERTY_TYPE_BASICSUPPORT | KSPROPERTY_TYPE_GET, VT_ILLEGAL);
            } else {
                ULONG cbNeeded = sizeof(KSMULTIPLE_ITEM) + sizeof(KSJACK_DESCRIPTION);

                if (PropertyRequest->ValueSize == 0) {
                    PropertyRequest->ValueSize = cbNeeded;
                    ntStatus = STATUS_BUFFER_OVERFLOW;
                } else if (PropertyRequest->ValueSize < cbNeeded) {
                    ntStatus = STATUS_BUFFER_TOO_SMALL;
                } else {
                    if (PropertyRequest->Verb & KSPROPERTY_TYPE_GET) {
                        PKSMULTIPLE_ITEM pMI = (PKSMULTIPLE_ITEM)PropertyRequest->Value;
                        PKSJACK_DESCRIPTION pDesc = (PKSJACK_DESCRIPTION)(pMI+1);

                        pMI->Size = cbNeeded;
                        pMI->Count = 1;

                        RtlCopyMemory(pDesc, JackDescriptions[nPinId], sizeof(KSJACK_DESCRIPTION));
                        BOOLEAN mic_avail = !!(GetSavedStatus() & IVSHMEM_STATUS_MIC_AVAIL_BIT);
                        pDesc->IsConnected = mic_avail;
                        ntStatus = STATUS_SUCCESS;
                    }
                }
            }
        }
    }

    return ntStatus;
}

//=============================================================================
NTSTATUS CMiniportTopology::PropertyHandlerJackDescription2(IN PPCPROPERTY_REQUEST PropertyRequest)
/*++
Routine Description:
  Handles ( KSPROPSETID_Jack, KSPROPERTY_JACK_DESCRIPTION )

Arguments:
  PropertyRequest - property request structure

Return Value:
  NT status code.
--*/
{
    ASSERT(PropertyRequest);

    DPF_ENTER(("[PropertyHandlerJackDescription2]"));

    NTSTATUS ntStatus = STATUS_INVALID_DEVICE_REQUEST;
    ULONG    nPinId = (ULONG)-1;

    if (PropertyRequest->InstanceSize < sizeof(ULONG)) {
        return ntStatus;
    }
    nPinId = *(PULONG(PropertyRequest->Instance));
    if ((nPinId < ARRAYSIZE(JackDescriptions)) && (JackDescriptions[nPinId] != NULL)) {
        if (PropertyRequest->Verb & KSPROPERTY_TYPE_BASICSUPPORT) {
            ntStatus = PropertyHandler_BasicSupport(PropertyRequest, KSPROPERTY_TYPE_BASICSUPPORT | KSPROPERTY_TYPE_GET, VT_ILLEGAL);
        } else {
            ULONG cbNeeded = sizeof(KSMULTIPLE_ITEM) + sizeof(KSJACK_DESCRIPTION2);
            if (PropertyRequest->ValueSize == 0) {
                PropertyRequest->ValueSize = cbNeeded;
                ntStatus = STATUS_BUFFER_OVERFLOW;
            } else if (PropertyRequest->ValueSize < cbNeeded) {
                ntStatus = STATUS_BUFFER_TOO_SMALL;
            } else if (PropertyRequest->Verb & KSPROPERTY_TYPE_GET) {
                PKSMULTIPLE_ITEM pMI = (PKSMULTIPLE_ITEM)PropertyRequest->Value;
                PKSJACK_DESCRIPTION2 pDesc = (PKSJACK_DESCRIPTION2)(pMI + 1);

                pMI->Size = cbNeeded;
                pMI->Count = 1;

                RtlZeroMemory(pDesc, sizeof(KSJACK_DESCRIPTION2));
                pDesc->DeviceStateInfo = 0;
                pDesc->JackCapabilities = JACKDESC2_PRESENCE_DETECT_CAPABILITY;
                ntStatus = STATUS_SUCCESS;
            }
        }
    }

    return ntStatus;
}

//=============================================================================
NTSTATUS PropertyHandler_TopoFilter(IN PPCPROPERTY_REQUEST PropertyRequest)
/*++
Routine Description:
  Redirects property request to miniport object

Arguments:
  PropertyRequest - 

Return Value:
  NT status code.
--*/
{
    ASSERT(PropertyRequest);

    DPF_ENTER(("[PropertyHandler_TopoFilter]"));

    // PropertryRequest structure is filled by portcls. 
    // MajorTarget is a pointer to miniport object for miniports.
    //
    NTSTATUS            ntStatus = STATUS_INVALID_DEVICE_REQUEST;
    PCMiniportTopology  pMiniport = (PCMiniportTopology)(PMINIPORTTOPOLOGY)PropertyRequest->MajorTarget;

    if (IsEqualGUIDAligned(*PropertyRequest->PropertyItem->Set, KSPROPSETID_Jack)) {
        switch (PropertyRequest->PropertyItem->Id) {
            case KSPROPERTY_JACK_DESCRIPTION:
                ntStatus = pMiniport->PropertyHandlerJackDescription(PropertyRequest);
                break;
            case KSPROPERTY_JACK_DESCRIPTION2:
                ntStatus = pMiniport->PropertyHandlerJackDescription2(PropertyRequest);
                break;
        }
    }

    return ntStatus;
} // PropertyHandler_TopoFilter

//=============================================================================
NTSTATUS PropertyHandler_Topology(IN PPCPROPERTY_REQUEST PropertyRequest)
/*++
Routine Description:
  Redirects property request to miniport object

Arguments:
  PropertyRequest - 

Return Value:
  NT status code.
--*/
{
    ASSERT(PropertyRequest);

    DPF_ENTER(("[PropertyHandler_Topology]"));

    // PropertryRequest structure is filled by portcls. 
    // MajorTarget is a pointer to miniport object for miniports.
    //
    return ((PCMiniportTopology)(PMINIPORTTOPOLOGY)
        (PropertyRequest->MajorTarget))->PropertyHandlerGeneric(PropertyRequest);
} // PropertyHandler_Topology

VOID CMiniportTopology::AddEventToEventList(IN PKSEVENT_ENTRY EventEntry)
{
    DPF_ENTER(("[CMiniportTopology::AddEventToEventList]"));

    ASSERT(m_PortEvents != NULL);

    m_PortEvents->AddEventToEventList(EventEntry);
}

NTSTATUS CMiniportTopology::PropertyHandlerEvent(IN PPCEVENT_REQUEST EventRequest)
{
    ASSERT(EventRequest);

    DPF_ENTER(("[PropertyHandler_HandlerEvent]"));

    // The major target is the object pointer to the topology miniport.
    PCMiniportTopology  pMiniport = (PCMiniportTopology)(PMINIPORTTOPOLOGY)EventRequest->MajorTarget;
    ASSERT(pMiniport);

    switch (EventRequest->Verb)
    {
    case PCEVENT_VERB_SUPPORT:;
        break;

    case PCEVENT_VERB_ADD:
        if (EventRequest->EventEntry) {
            pMiniport->AddEventToEventList(EventRequest->EventEntry);
        } else {
            return STATUS_UNSUCCESSFUL;
        }
        break;
    case PCEVENT_VERB_REMOVE:
        DPF(D_VERBOSE, ("Removing event."));
        break;
    default:
        return STATUS_INVALID_PARAMETER;
    }

    return STATUS_SUCCESS;
}

UINT32 CMiniportTopology::GetStatusFromBackend()
{
    return m_AdapterCommon->GetStatusFromBackend();
}

NTSTATUS CMiniportTopology::InitializeWorkItems(PDEVICE_OBJECT DeviceObject)
{
    NTSTATUS ntStatus = STATUS_SUCCESS;

    if (m_pWorkItems != NULL) {
        return ntStatus;
    }

    if (DeviceObject == NULL) {
        return STATUS_INVALID_PARAMETER;
    }

    m_pWorkItems = (PWORKITEM_PARAM)
        ExAllocatePoolWithTag(NonPagedPool, sizeof(WORKITEM_PARAM) * MAX_WORKER_ITEM_COUNT, MSVAD_POOLTAG);
    if (m_pWorkItems) {
        for (int i = 0; i < MAX_WORKER_ITEM_COUNT; i++) {
            m_pWorkItems[i].WorkItem = IoAllocateWorkItem(DeviceObject);
            if (m_pWorkItems[i].WorkItem == NULL) {
                return STATUS_INSUFFICIENT_RESOURCES;
            }
            InterlockedExchange(&m_pWorkItems[i].Used, 0);
        }
    } else {
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
    }

    return ntStatus;
}

void CMiniportTopology::DestroyWorkItems()
{
    if (m_pWorkItems == NULL) {
        return;
    }

    for (int i = 0; i < MAX_WORKER_ITEM_COUNT; i++) {
        if (m_pWorkItems[i].WorkItem == NULL) {
            continue;
        }
        IoFreeWorkItem(m_pWorkItems[i].WorkItem);
        m_pWorkItems[i].WorkItem = NULL;
    }
    ExFreePoolWithTag(m_pWorkItems, MSVAD_POOLTAG);
    m_pWorkItems = NULL;
}

PWORKITEM_PARAM CMiniportTopology::GetWorkItem()
{
    // This function is called at DPC level, locked by IVSHMem spinlock.
    // There is no concurrency issue.
    if (!m_pWorkItems) {
        return NULL;
    }

    for (int i = 0; i < MAX_WORKER_ITEM_COUNT; i++) {
        if (!m_pWorkItems[i].Used && m_pWorkItems[i].WorkItem) {
            InterlockedExchange(&m_pWorkItems[i].Used, 1);
            return &m_pWorkItems[i];
        }
    }
    return NULL;
}

NTSTATUS PropertyHandler_TopoNodeEvent(IN PPCEVENT_REQUEST EventRequest)
{
    ASSERT(EventRequest);

    DPF_ENTER(("[PropertyHandler_TopoNodeEvent]"));

    // Validate the node.
    if (EventRequest->Node != KSNODE_TOPO_LINEOUT_VOLUME && EventRequest->Node != KSNODE_TOPO_WAVEOUT_VOLUME)
        return STATUS_INVALID_PARAMETER;

    return ((PCMiniportTopology)(PMINIPORTTOPOLOGY)(EventRequest->MajorTarget))->PropertyHandlerEvent(EventRequest);
}

NTSTATUS PropertyHandler_TopoFilterEvent(IN PPCEVENT_REQUEST EventRequest)
{
    ASSERT(EventRequest);

    DPF_ENTER(("[PropertyHandler_TopoFilterEvent]"));

    return ((PCMiniportTopology)(PMINIPORTTOPOLOGY)(EventRequest->MajorTarget))->PropertyHandlerEvent(EventRequest);
}
